unit database_actors;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, StdCtrls, ExtCtrls, Spin, Buttons, fpjson,
  Graphics, Dialogs, database_base, globals, database, gamefont,
  traitlist, itemselectorframeunit, LCLTranslator;

type

  { TDatabaseActorsFrame }

  TDatabaseActorsFrame = class(TDatabaseBaseFrame)
    CharacterGraphicsLabel: TLabel;
    CharacterGraphicsSpeedButton: TSpeedButton;
    ClassComboBox: TComboBox;
    ClassLabel: TLabel;
    ActorSelectorFrame: TItemSelectorFrame;
    NicknameLabel: TLabel;
    NicknameEdit: TEdit;
    NameLabel: TLabel;
    NameEdit: TEdit;
    FaceLabel: TLabel;
    FaceSpeedButton: TSpeedButton;
    InitialEquipmentFlowPanel: TFlowPanel;
    NotesMemo: TMemo;
    NotesGroupBox: TGroupBox;
    InitialEquipmentScrollBox: TScrollBox;
    NotesGroupBoxSplitter: TSplitter;
    ActorSelectorSplitter: TSplitter;
    ActorTraitListFrame: TTraitListFrame;
    TraitsGroupBox: TGroupBox;
    ImagesGroupBox: TGroupBox;
    ActorContentsPanel: TPanel;
    InitialEquipmentGroupBox: TGroupBox;
    InitialLevelLabel: TLabel;
    InitialLevelSpinEdit: TSpinEdit;
    MainContentScrollBox: TScrollBox;
    MainDataPanel: TPanel;
    MainSettingsGroupBox: TGroupBox;
    MaximumLevelLabel: TLabel;
    MaximumLevelSpinEdit: TSpinEdit;
    AdditionalDataPanel: TPanel;
    ProfileLabel: TLabel;
    ProfileMemo: TMemo;
    SvBattlerLabel: TLabel;
    SvBattlerSpeedButton: TSpeedButton;
    procedure CharacterGraphicsSpeedButtonClick(Sender: TObject);
    procedure ClassComboBoxChange(Sender: TObject);
    procedure FaceSpeedButtonClick(Sender: TObject);
    procedure Init; override;
    procedure InitialLevelSpinEditChange(Sender: TObject);
    procedure LoadClasses;
    procedure MaximumLevelSpinEditChange(Sender: TObject);
    procedure NameEditChange(Sender: TObject);
    procedure NicknameEditChange(Sender: TObject);
    procedure NotesMemoChange(Sender: TObject);
    procedure ProfileMemoChange(Sender: TObject);
    procedure LoadActorData(NewId: Integer);
    procedure LoadActorImages;
    procedure LoadActorFace;
    procedure LoadActorCharacterGraphics;
    procedure LoadActorSvBattlerGraphics;
    procedure ResizeMemo;
    { Remove the equip panels, labels and comboboxes. }
    procedure ClearEquips;
    { Create equip panels, labels and comboboxes, filling the InitialEquipPanels,
      InitialEquipLabels and InitialEquipLabels arrays (but the element #0 will
      always be zero). }
    procedure CreateEquips;

    function GetSelectedActor(FullyLoaded: Boolean = True): TJSONObject;
    procedure SvBattlerSpeedButtonClick(Sender: TObject);
    procedure InitialEquipComboBoxChange(Sender: TObject);
  private
    { Panels holding initial equips. Element #0 is always nil }
    InitialEquipPanels: array of TPanel;
    { Labels with the initial equip names. Element #0 is always nil }
    InitialEquipLabels: array of TLabel;
    { Comboboxes with the available initial equips. Element #0 is always nil }
    InitialEquipComboBoxes: array of TComboBox;
    { Array of arrays, each sub-array storing IDs for elements in the combobox }
    InitialEquipIds: array of TIntList;

    IsLoading: Boolean;
    SelectedId: Integer;
  public
    destructor Destroy; override;
    procedure UpdateLanguage(Code: String); override;
  end;

resourcestring
  ActorsHeader = 'Actors';

implementation

uses imageselection, languagehelper;

{$R *.lfm}

procedure TDatabaseActorsFrame.Init;
begin
  IsLoading := True;
  ActorSelectorFrame.SetData(Db.Actors, @Db.ResizeActors, @LoadActorData);
  ActorSelectorFrame.Title := ActorsHeader;
  ActorSelectorFrame.JsonDataType := 'application/rpgmv-actors';
  ActorSelectorFrame.CreateElementCallback := @Db.CreateEmptyActor;
  LoadClasses;

  ActorTraitListFrame.SetDatabase(Db);

  ResizeMemo;

  LoadActorData(1);
end;

procedure TDatabaseActorsFrame.ClearEquips;
var
  I: Integer;
begin
  for I := 1 to High(InitialEquipPanels) do begin
    InitialEquipPanels[I].Free;
    InitialEquipLabels[I].Free;
    InitialEquipComboBoxes[I].Free;
  end;

  SetLength(InitialEquipPanels, 0);
  SetLength(InitialEquipLabels, 0);
  SetLength(InitialEquipComboBoxes, 0);
  for I := Low(InitialEquipIds) to High(InitialEquipIds) do begin
    if InitialEquipIds[I] <> nil then
      InitialEquipIds[I].Free;
  end;
  SetLength(InitialEquipIds, 0);
end;

procedure TDatabaseActorsFrame.CreateEquips;
var
  Actor: TJSONObject;

  EquipTypes: TJSONArray;
  I: Integer;

  procedure FillEquipList(ComboBox: TComboBox; Index: Integer; EquipType: Integer);
  var
    WTypes: TIntList = nil;
    ATypes: TIntList = nil;
    Equipables: TJSONArray;
    Equipable: TJSONObject;
    CorrectEquipType, CorrectAOrWType: Boolean;
    EquipableIndex: Integer;

    CurrentValue: Integer = 0;
    InitialEquips: TJSONArray;
  begin
   if (Actor.Find('equips', InitialEquips)) and (InitialEquips.JSONType = jtArray) then
     if Index - 1 < InitialEquips.Count - 1 then begin
       CurrentValue := InitialEquips.Integers[Index - 1];
     end;

   InitialEquipIds[Index] := TIntList.Create;

   ComboBox.Items.Add('(None)');
   InitialEquipIds[Index].Add(0);
   if CurrentValue = 0 then
     ComboBox.ItemIndex := 0;

   if EquipType = 1 then begin
     WTypes := Db.GetActorWeaponTypes(Actor.Integers['id']);
     Equipables := Db.Weapons;
   end
   else begin
     ATypes := Db.GetActorArmourTypes(Actor.Integers['id']);
     Equipables := Db.Armours;
   end;

   for EquipableIndex := 1 to Equipables.Count -1 do begin
     Equipable := Equipables.Objects[EquipableIndex];

     if EquipType = 1 then begin
       CorrectAOrWType := WTypes.IndexOf(Equipable.Integers['wtypeId']) > -1;
     end
     else begin
       CorrectAOrWType := ATypes.IndexOf(Equipable.Integers['atypeId']) > -1;
     end;

     CorrectEquipType := Equipable.Integers['etypeId'] = EquipType;

     if CorrectAOrWType and CorrectEquipType then begin
       ComboBox.Items.Add(TDatabase.FormatObjName(Equipable));
       InitialEquipIds[Index].Add(Equipable.Integers['id']);
       if CurrentValue = Equipable.Integers['id'] then
         ComboBox.ItemIndex := ComboBox.Items.Count - 1;
     end;

   end;
  end;

  procedure CreateEquip(Index: Integer; EquipType: Integer);
  var
    EquipPanel: TPanel;
    EquipLabel: TLabel;
    EquipComboBox: TComboBox;
  begin
    EquipPanel := TPanel.Create(Self);
    EquipPanel.Parent := InitialEquipmentFlowPanel;
    EquipPanel.Width := InitialEquipmentFlowPanel.Width div 2 - 5;
    EquipPanel.Tag := Index;
    EquipPanel.BevelOuter := bvNone;

    EquipLabel := TLabel.Create(Self);
    EquipLabel.Parent := EquipPanel;
    EquipLabel.Caption := EquipTypes.Strings[EquipType];
    EquipLabel.Left := 2;
    EquipLabel.Top := 2;

    EquipComboBox := TComboBox.Create(Self);
    EquipComboBox.Parent := EquipPanel;
    EquipComboBox.Left := 2;
    EquipComboBox.Top := EquipLabel.Height + 1;
    EquipComboBox.Width := EquipPanel.ClientWidth - 4;
    EquipComboBox.Style := csDropDownList;
    EquipComboBox.Tag := Index;
    FillEquipList(EquipComboBox, Index, EquipType);
    EquipComboBox.OnChange := @InitialEquipComboBoxChange;

    EquipPanel.ClientHeight := EquipComboBox.Top + EquipComboBox.Height + 2;

    if IsFlipped then
      EquipPanel.FlipChildren(True);

    EquipLabel.Show;
    EquipComboBox.Show;
    EquipPanel.Show;

    InitialEquipPanels[Index] := EquipPanel;
    InitialEquipLabels[Index] := EquipLabel;
    InitialEquipComboBoxes[Index] := EquipComboBox;
  end;

begin
  if Db.System.Find('equipTypes', EquipTypes) then begin
    Actor := GetSelectedActor(False);

    SetLength(InitialEquipPanels, EquipTypes.Count);
    SetLength(InitialEquipLabels, EquipTypes.Count);
    SetLength(InitialEquipComboBoxes, EquipTypes.Count);
    SetLength(InitialEquipIds, EquipTypes.Count);

    for I := 1 to EquipTypes.Count -1 do begin
      if (I = 2) and Db.ActorHasTwoWeapons(Actor.Integers['id']) then
        CreateEquip(2, 1) //replace equip #2 (shield) with #1 (weapon)
      else
        CreateEquip(I, I);
    end;
  end;
end;

procedure TDatabaseActorsFrame.InitialLevelSpinEditChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then
    Actor.Integers['initialLevel'] := InitialLevelSpinEdit.Value;
end;

procedure TDatabaseActorsFrame.LoadClasses;
var
  I: Integer;
  ClassObj: TJSONObject;
begin
  ClassComboBox.Items.BeginUpdate;
  ClassComboBox.Items.Clear;
  for I := 1 to Db.Classes.Count -1 do begin
    ClassObj := Db.Classes.Objects[I];
    ClassComboBox.Items.Append(IntToStr(I) + ' ' + ClassObj.Strings['name']);
  end;
  ClassComboBox.Items.EndUpdate;
end;

procedure TDatabaseActorsFrame.MaximumLevelSpinEditChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then
    Actor.Integers['maxLevel'] := MaximumLevelSpinEdit.Value;
end;

procedure TDatabaseActorsFrame.NameEditChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then begin
     Actor.Strings['name'] := NameEdit.Text;
     ActorSelectorFrame.RefreshSelectedName;
  end;
end;

procedure TDatabaseActorsFrame.NicknameEditChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then begin
     Actor.Strings['nickname'] := NicknameEdit.Text;
  end;
end;

procedure TDatabaseActorsFrame.NotesMemoChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then begin
     Actor.Strings['note'] := NotesMemo.Text;
  end;
end;

procedure TDatabaseActorsFrame.ProfileMemoChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then begin
     Actor.Strings['profile'] := ProfileMemo.Text;
  end;
end;

procedure TDatabaseActorsFrame.CharacterGraphicsSpeedButtonClick(Sender: TObject
  );
var
  Actor: TJSONObject;
  CharNameJson: TJSONString;
  CharIndexJson: TJSONNumber;

  CharName: String;
  CharIndex: Integer;
begin
  Actor := GetSelectedActor;
  if Actor = nil then
    Exit;

  CharName := '';
  CharIndex := 0;

  if Actor.Find('characterName', CharNameJson) then begin
    CharName := CharNameJson.AsString;

    if Actor.Find('characterIndex', CharIndexJson) then
      CharIndex := CharIndexJson.AsInteger;
  end;

  if ImageSelectionForm.ShowSimpleCharSelection(CharName, CharIndex) then begin
    Actor.Strings['characterName'] := ImageSelectionForm.SelectedFilename;
    Actor.Integers['characterIndex'] := ImageSelectionForm.Index;
    LoadActorCharacterGraphics;
  end;
end;

procedure TDatabaseActorsFrame.ClassComboBoxChange(Sender: TObject);
var
  Actor: TJSONObject;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then
    Actor.Integers['classId'] := ClassComboBox.ItemIndex + 1;
end;

procedure TDatabaseActorsFrame.InitialEquipComboBoxChange(Sender: TObject);
var
  Actor: TJSONObject;
  EquipTypeId: Integer;
  Box: TComboBox;
  EquipableId: Integer;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then begin
    Box := Sender as TComboBox;

    EquipTypeId := Integer(Box.Tag);
    EquipableId := InitialEquipIds[EquipTypeId][Box.ItemIndex];

    Actor.Arrays['equips'].Integers[EquipTypeId-1] := EquipableId;
  end;
end;

procedure TDatabaseActorsFrame.FaceSpeedButtonClick(Sender: TObject);
var
  Actor: TJSONObject;
  FaceNameJson: TJSONString;
  FaceIndexJson: TJSONNumber;

  FaceName: String;
  FaceIndex: Integer;
begin
  Actor := GetSelectedActor;
  if Actor = nil then
    Exit;

  FaceName := '';
  FaceIndex := 0;

  if Actor.Find('faceName', FaceNameJson) then begin
    FaceName := FaceNameJson.AsString;

    if Actor.Find('faceIndex', FaceIndexJson) then
      FaceIndex := FaceIndexJson.AsInteger;

  end;

  if ImageSelectionForm.ShowFaceSelection(FaceName, FaceIndex) then begin
    Actor.Strings['faceName'] := ImageSelectionForm.SelectedFilename;
    Actor.Integers['faceIndex'] := ImageSelectionForm.Index;
    LoadActorFace;
  end;
end;

procedure TDatabaseActorsFrame.SvBattlerSpeedButtonClick(Sender: TObject);
var
  Actor: TJSONObject;
  BattlerNameJson: TJSONString;
  BattlerName: String;
begin
  Actor := GetSelectedActor;
  if Actor <> nil then begin
    BattlerName := '';

    if Actor.Find('battlerName', BattlerNameJson) then begin
      BattlerName := BattlerNameJson.AsString;
    end;

    if ImageSelectionForm.ShowSvBattlerSelection(BattlerName) then begin
      Actor.Strings['battlerName'] := ImageSelectionForm.SelectedFilename;
      LoadActorSvBattlerGraphics;
    end;
  end;
end;

procedure TDatabaseActorsFrame.LoadActorData(NewId: Integer);
var
  Actor: TJSONObject;
  ActorName, Nickname, Profile, Note: TJSONString;
  ClassId, InitialLevel, MaximumLevel: TJSONNumber;
  TraitList: TJSONArray;
begin
  SelectedId := NewId;
  ActorContentsPanel.Hide;
  IsLoading := True;

  Actor := GetSelectedActor(False);
  if Actor <> nil then begin
    if Actor.Find('name', ActorName) then
      NameEdit.Text := ActorName.AsString
    else
      NameEdit.Text := '';

    if Actor.Find('nickname', Nickname) then
      NicknameEdit.Text := Nickname.AsString
    else
      NicknameEdit.Text := '';

    if Actor.Find('classId', ClassId) then begin
      if ClassId.AsInteger - 1 < ClassComboBox.Items.Count then
        ClassComboBox.ItemIndex := ClassId.AsInteger - 1
      else
        ClassComboBox.ItemIndex := -1;
    end
    else
      NameEdit.Text := '';

    if Actor.Find('initialLevel', InitialLevel) then
      InitialLevelSpinEdit.Value := InitialLevel.AsInteger
    else
      InitialLevelSpinEdit.Value := 1;

    if Actor.Find('maxLevel', MaximumLevel) then
      MaximumLevelSpinEdit.Value := MaximumLevel.AsInteger
    else
      MaximumLevelSpinEdit.Value := 1;

    if Actor.Find('profile', Profile) then
      ProfileMemo.Text := Profile.AsString
    else
      ProfileMemo.Text := '';

    if Actor.Find('note', Note) then
      NotesMemo.Text := Note.AsString
    else
      NotesMemo.Text := '';

    if Actor.Find('traits', TraitList) then
      ActorTraitListFrame.SetTraitList(TraitList)
    else
      ActorTraitListFrame.SetTraitList(nil);


    LoadActorImages;
  end
  else begin
    NameEdit.Text := '';
    NicknameEdit.Text := '';
    ProfileMemo.Text := '';
    NotesMemo.Text := '';
    ActorTraitListFrame.SetTraitList(nil);
  end;

  ClearEquips;
  CreateEquips;

  IsLoading := False;
  ActorContentsPanel.Show;
end;

procedure TDatabaseActorsFrame.LoadActorImages;
begin
  LoadActorFace;
  LoadActorCharacterGraphics;
  LoadActorSvBattlerGraphics;
end;

procedure TDatabaseActorsFrame.LoadActorFace;
var
  Actor: TJSONObject;
  FaceName: TJSONString;
  FaceIndex: TJSONNumber;
  FacePng: TBitmap;
begin
  Actor := GetSelectedActor(False);

  FaceSpeedButton.Glyph.Clear;
  if Actor.Find('faceName', FaceName)
                               and Actor.Find('faceIndex', FaceIndex) then begin
    if FaceName.AsString <> '' then begin
      FacePng := Game.GetFace(FaceName.AsString, FaceIndex.AsInteger, 74);
      FaceSpeedButton.Glyph.Assign(FacePng);
      FacePng.Free;
    end;
  end;
end;

procedure TDatabaseActorsFrame.LoadActorCharacterGraphics;
var
  Actor: TJSONObject;
  CharName: TJSONString;
  CharIndex: TJSONNumber;
  Png: TBitmap;
begin
  Actor := GetSelectedActor(False);

  CharacterGraphicsSpeedButton.Glyph.Clear;
  if Actor.Find('characterName', CharName)
                          and Actor.Find('characterIndex', CharIndex) then begin
    if CharName.AsString <> '' then begin
      Png := Game.GetCharacterGraphics(CharName.AsString, CharIndex.AsInteger);
      CharacterGraphicsSpeedButton.Glyph.Assign(Png);
      Png.Free;
    end;
  end;
end;

procedure TDatabaseActorsFrame.LoadActorSvBattlerGraphics;
var
  Actor: TJSONObject;
  BattlerName: TJSONString;
  Png: TBitmap;
begin
  Actor := GetSelectedActor(False);

  SvBattlerSpeedButton.Glyph.Clear;
  if Actor.Find('battlerName', BattlerName) then begin
    if BattlerName.AsString <> '' then begin
      Png := Game.GetSvBattlerGraphics(BattlerName.AsString);
      SvBattlerSpeedButton.Glyph.Assign(Png);
      Png.Free;
    end;
  end;
end;

procedure TDatabaseActorsFrame.ResizeMemo;
begin
  ProfileMemo.Font.BeginUpdate;
  ProfileMemo.Font.Name := GetMonospacedFontName;
  ProfileMemo.Font.Size := GetMonospacedFontSize;
  ProfileMemo.Font.EndUpdate;
  ResizeMemoForFont(ProfileMemo, 55, 2);
end;

function TDatabaseActorsFrame.GetSelectedActor(FullyLoaded: Boolean = True): TJSONObject;
var
  LoadCheck: Boolean;
begin
  GetSelectedActor := nil;
  LoadCheck := not FullyLoaded or not IsLoading;

  if LoadCheck and (SelectedId >= 1) and (SelectedId < Db.Actors.Count) then begin
    GetSelectedActor := Db.Actors[SelectedId] as TJSONObject;
  end;
end;

destructor TDatabaseActorsFrame.Destroy;
var
  I: Integer;
begin
  for I := Low(InitialEquipIds) to High(InitialEquipIds) do begin
    if InitialEquipIds[I] <> nil then
      InitialEquipIds[I].Free;
  end;
  SetLength(InitialEquipIds, 0);

  inherited;
end;

procedure TDatabaseActorsFrame.UpdateLanguage(Code: String);
begin
  inherited UpdateLanguage(Code);

  if IsLanguageRtl(Code) then begin
    InitialEquipmentFlowPanel.FlowStyle := fsRightLeftTopBottom;
  end else begin
    InitialEquipmentFlowPanel.FlowStyle := fsLeftRightTopBottom;
  end;
end;

end.


